"""
大模型买卖策略分析服务
整合K线图生成、上下文信息和提示词，调用DeepSeek进行视觉分析
"""

import base64
import json
import os
import requests
from typing import Dict, List, Optional
from datetime import datetime
from ..services.binance_client import BinanceDataClient, SUPPORTED_INTERVALS
from ..services.chart_service import ChartService
from ..config import config


class AIStrategyService:
    """大模型买卖策略分析服务"""

    def __init__(self) -> None:
        """初始化服务"""
        self.binance_client = BinanceDataClient()
        self.chart_service = ChartService()
        from ..config import config
        self.deepseek_api_key = config.DEEPSEEK_API_KEY
        self.deepseek_api_url = config.DEEPSEEK_API_URL

    def analyze_klines_with_ai(
        self,
        symbol: str,
        interval: SUPPORTED_INTERVALS,
        limit: int = 30,
        chart_type: str = 'candlestick',
        existing_chart_path: Optional[str] = None,
        klines_data: Optional[List[Dict]] = None,
        current_position: Optional[Dict] = None
    ) -> Dict:
        """
        使用大模型分析K线图并生成买卖策略

        Args:
            symbol: 交易对符号（如 'BTCUSDT'）
            interval: 时间周期 ('5m', '1h', '4h')
            limit: K线数量，默认30
            chart_type: 图表类型，默认'candlestick'
            existing_chart_path: 已存在的图表路径（可选）
            klines_data: 传入的K线数据（可选），如果提供则优先使用
            current_position: 当前持仓信息（可选），格式: {'position_type': 'long/short', 'open_price': float}

        Returns:
            包含买卖策略的字典
        """
        try:
            # 1. 获取K线数据（优先使用传入的数据）
            klines = klines_data if klines_data is not None else self.binance_client.get_klines(
                symbol=symbol,
                interval=interval,
                limit=limit
            )

            if not klines:
                raise ValueError(f"未能获取到 {symbol} 的K线数据")

            # 2. 生成或使用已有K线图
            if existing_chart_path and os.path.exists(existing_chart_path):
                chart_path = existing_chart_path
            else:
                chart_path = self.chart_service.generate_candlestick_chart(
                    symbol=symbol,
                    klines_data=klines,
                    chart_type=chart_type,
                    show_volume=True
                )

            # 3. 构建上下文信息
            context_info = self._build_context_info(symbol, interval, limit, klines)

            # 4. 构建提示词（传入当前持仓信息）
            prompt = self._build_prompt(context_info, current_position)

            # 5. 调用DeepSeek进行视觉分析（带图片）
            analysis_result = self._call_deepseek_vision_analysis(chart_path, prompt)

            # 6. 解析并返回结果
            return {
                'code': 200,
                'data': {
                    'symbol': symbol,
                    'interval': interval,
                    'chart_path': chart_path,
                    'context': context_info,
                    'analysis': analysis_result
                },
                'msg': 'success'
            }

        except Exception as e:
            return {
                'code': 500,
                'data': {},
                'msg': f'AI分析失败: {str(e)}'
            }

    def _build_context_info(
        self,
        symbol: str,
        interval: str,
        limit: int,
        klines: list
    ) -> Dict:
        """
        构建上下文信息

        Args:
            symbol: 交易对
            interval: 时间周期
            limit: K线数量
            klines: K线数据

        Returns:
            包含上下文信息的字典
        """
        if not klines:
            return {}

        latest = klines[-1]
        earliest = klines[0]

        # 计算价格变化
        price_change = latest['close'] - earliest['open']
        price_change_pct = (price_change / earliest['open']) * 100

        # 计算成交量统计
        total_volume = sum(k['volume'] for k in klines)
        avg_volume = total_volume / len(klines)

        # 时间范围
        start_time = datetime.fromtimestamp(earliest['open_time'] / 1000)
        end_time = datetime.fromtimestamp(latest['close_time'] / 1000)

        return {
            'symbol': symbol,
            'interval': interval,
            'kline_count': len(klines),
            'price_range': {
                'start_price': f"{earliest['open']:.2f}",
                'end_price': f"{latest['close']:.2f}",
                'highest': f"{max(k['high'] for k in klines):.2f}",
                'lowest': f"{min(k['low'] for k in klines):.2f}"
            },
            'price_change': {
                'absolute': f"{price_change:.2f}",
                'percentage': f"{price_change_pct:.2f}%"
            },
            'volume_stats': {
                'total': f"{total_volume:.2f}",
                'average': f"{avg_volume:.2f}"
            },
            'time_range': {
                'start': start_time.strftime('%Y-%m-%d %H:%M'),
                'end': end_time.strftime('%Y-%m-%d %H:%M')
            },
            'latest_kline': {
                'open': latest['open'],
                'high': latest['high'],
                'low': latest['low'],
                'close': latest['close'],
                'volume': latest['volume']
            }
        }

    def _build_prompt(self, context_info: Dict, current_position: Optional[Dict] = None) -> str:
        """
        构建发送给大模型的提示词

        Args:
            context_info: 上下文信息
            current_position: 当前持仓信息（可选），格式: {'position_type': 'long/short', 'open_price': float}

        Returns:
            完整的提示词
        """
        prompt = f"""# 角色
你是Al Brooks，价格行为交易专家。你是《Price Action Trading》一书的作者，以精准的趋势识别和顺势交易著称。

# Al Brooks价格行为交易系统

## 1. 趋势识别与分析
- **上涨趋势**: 连续出现更高高(HH)和更高低(HL)，价格向上推进
- **下跌趋势**: 连续出现更低高(LH)和更低低(LL)，价格向下推进
- **震荡区间**: 价格在区间内反复，无明确方向
- **✅ 关键**: 趋势启动时及时抓住机会！

## 2. 摆动点与关键价位
- **摆动高点**: 比前后各2-3根K线都高的显著高点
- **摆动低点**: 比前后各2-3根K线都低的显著低点
- **支撑位**: 前期重要的摆动低点，反弹的参考位置
- **阻力位**: 前期重要的摆动高点，回调的参考位置

## 3. 做多入场信号（Al Brooks风格）
**满足以下条件即可入场做多**：
- **趋势基础**: 明确的上涨趋势或趋势启动初期
- **入场时机**:
  - 趋势中的回调在支撑位附近出现反转信号
  - 突破前高且成交量放大
  - 强势上涨后的短暂停（第二段上涨）
- **反转信号**: 下影线、吞没形态、十字星等
- **风险控制**: 设置合理止损在关键支撑下方

⚠️ **反趋势陷阱规避（做多时需要平衡执行）**：
- **大涨后首次回调谨慎做空**: 如果之前有强势上涨(>8%)且当前回调幅度<3%，**这可能是正常回调**，不要急于做空，观察是否能在支撑位企稳
- **2 Leg Trap精准识别**: 只有在震荡上行中出现连续的上升-回调-再上升结构，且**同时出现至少两个**以下特征才认为是陷阱：
  - 第二次上升未能创新高（最高点低于第一次上升的最高点）
  - 创新高但成交量明显萎缩（<第一次上升成交量的50%）
  - 第二次上升力度明显减弱（涨幅<第一次的50%）
  - 回调幅度过深（>第一次上升幅度的60%）
  **单边强势趋势中，这些规则不适用！**
- **强势趋势确认与决策**:
  - **强势上涨趋势特征**: 连续3段以上上涨，总涨幅>8%，成交量持续放大，回调幅度<2%
  - **强势趋势中的决策**: 在明确的强势上涨趋势中，即使有小幅回调也要**果断做多**，不要犹豫
  - **核心原则**: **趋势为王！陷阱规避服务于顺势交易，不要因过度警惕而错过大趋势！**

## 4. 做空入场信号（Al Brooks风格）
**满足以下条件即可入场做空**：
- **趋势基础**: 明确的下跌趋势或趋势启动初期
- **入场时机**:
  - 趋势中的回调在阻力位附近出现反转信号
  - 跌破前低且成交量放大
  - 强势下跌后的短暂停（第二段下跌）
- **反转信号**: 上影线、吞没形态、十字星等
- **风险控制**: 设置合理止损在关键阻力上方

⚠️ **反趋势陷阱规避（做空时需要平衡执行）**：
- **大跌后首次反弹谨慎做多**: 如果之前有强势下跌(>8%)且当前反弹幅度<3%，**这可能是正常反弹**，不要急于做多，观察是否能在阻力位企稳
- **2 Leg Trap精准识别**: 只有在震荡下行中出现连续的下跌-反弹-再下跌结构，且**同时出现至少两个**以下特征才认为是陷阱：
  - 第二次下跌未能创新低（最低点高于第一次下跌的最低点）
  - 创新低但成交量明显萎缩（<第一次下跌成交量的50%）
  - 第二次下跌力度明显减弱（跌幅<第一次的50%）
  - 反弹幅度过深（>第一次下跌幅度的60%）
  **单边强势趋势中，这些规则不适用！**
- **强势趋势确认与决策**:
  - **强势下跌趋势特征**: 连续3段以上下跌，总跌幅>8%，成交量持续放大，反弹幅度<2%
  - **强势趋势中的决策**: 在明确的强势下跌趋势中，即使有小幅反弹也要**果断做空**，不要犹豫
  - **核心原则**: **趋势为王！陷阱规避服务于顺势交易，不要因过度警惕而错过大趋势！**

## 5. Al Brooks的交易哲学
- **顺势而为**: 永远顺着主要趋势方向交易
- **趋势跟踪**: 趋势一旦确认，要敢于追随
- **价格行为**: 通过K线形态读懂市场意图
- **风险第一**: 每笔交易都要有明确的止损
- **果断决策**: 看到信号及时行动，不要犹豫

## 6. 平仓策略
- **盈利目标**: 达到2:1或3:1盈亏比时考虑部分平仓
- **止损规则**: 触及止损立即平仓
- **趋势结束**: 关键趋势线被破坏时平仓
- **时间止损**: 入场后长时间无进展，考虑减仓
- **⭐ Measure Move目标**: 价格突破关键位后的精确目标位！**这是AI分析的核心价值，到达后必须坚决止盈！**
## 6.1 ⚠️ **平仓算法说明**:
- 判断当前仓位方向
- 如果持有为空仓，则当前市场数据的最高价>=开仓时的stoploss则为止损，当最低价<=开仓时的measureMove或最低价<=开仓时的takeProfit则为止盈
- 如果持有为多仓，则当前市场数据的最低价<=开仓时的stoploss则为止损，当最高价>=开仓时的measureMove或最高价>=开仓时的takeProfit则为止盈

## 7. 当前市场数据
交易对: {context_info.get('symbol', 'N/A')} | 时间周期: {context_info.get('interval', 'N/A')} | K线数量: {context_info.get('kline_count', 'N/A')}根
起始价格: {context_info.get('price_range', {}).get('start_price', 'N/A')} → 最新价格: {context_info.get('price_range', {}).get('end_price', 'N/A')} USDT
最高价: {context_info.get('price_range', {}).get('highest', 'N/A')} | 最低价: {context_info.get('price_range', {}).get('lowest', 'N/A')} | 涨跌幅: {context_info.get('price_change', {}).get('percentage', 'N/A')}
成交量: 总计{context_info.get('volume_stats', {}).get('total', 'N/A')} | 平均{context_info.get('volume_stats', {}).get('average', 'N/A')}

## 8. 当前持仓状态
{f'当前持仓: {current_position.get("position_type", "").upper()} @ {current_position.get("open_price", 0):.2f} USDT' if current_position else '当前持仓: 无持仓'}
{f'开仓时AI建议: stopLoss={current_position.get("stopLoss")}, takeProfit={current_position.get("takeProfit")}, measureMove={current_position.get("measureMove")}' if current_position else ''}

## 8.1 持仓决策规则（有持仓时必读！）
- **如果当前价格触及开仓时AI建议的stopLoss**: 必须返回"close"平仓止损
- **如果当前价格触及开仓时AI建议的takeProfit**: 必须返回"close"平仓止盈
- **如果当前价格触及开仓时AI建议的measureMove**: **必须返回"close"坚决止盈！**
- **如果趋势反转**: 返回"close"
- **其他情况**: 返回"hold"

## 9. Al Brooks实战分析流程
1. **读趋势**: 识别当前是上涨、下跌还是震荡
2. **找关键位**: 标记重要的支撑和阻力位置
3. **🔍 陷阱识别**（重要！避免假突破/假跌破）:
   - 检查是否存在2 Leg Trap特征
   - 确认是否为单边强势趋势（强势趋势中陷阱规则不适用）
   - 综合成交量、力度等多维度判断
4. **看时机**: 判断是否出现了合适的入场信号
5. **做决策**（必须是确定的决策！）:
   - 无持仓：趋势明确且有信号 → **LONG** 或 **SHORT**
   - 有持仓：趋势继续 → **HOLD**；趋势反转或触及止盈/止损 → **close**
   - 震荡或信号不明确 → **HOLD**

## 10. 关键提醒（Al Brooks风格）
- **趋势为王**: 永远顺着主要趋势方向交易
- **信号及时**: 趋势启动时敢于果断入场
- **止损第一**: 每笔交易必须有止损保护
- **⚠️ reason详细**: 用中文说明你的趋势判断和入场或平仓理由
- **⭐ 决策确定**: 行动要果断明确！不要犹豫，不要说"建议"！"可能"！"观望"！
- **⚖️ 平衡原则**: 在陷阱规避和趋势抓取之间保持平衡，**不要因过度警惕而错过大趋势**

## 11. 止损止盈要求（⭐⭐⭐ 非常重要 ⭐⭐⭐）
当建议 action 为 **long** 或 **short** 时，必须计算并提供**四个字段**：
- **stopLoss**: 止损价格（基于关键支撑/阻力位计算）
- **takeProfit**: 止盈价格（建议2:1或3:1的盈亏比）
- **⭐⭐⭐ measureMove: Measure Move目标位（核心！）⭐⭐⭐**
  - 这是Al Brooks Price Action理论的核心概念
  - 价格突破关键位后的精确目标位置
  - 通常等于之前波动区间的距离
  - **到达后必须坚决止盈，不得犹豫！**
  - **重要：判断是否到达时，必须使用当日最高/最低价，而非收盘价！**

止损止盈计算原则：
- **做多(long)**:
  - stopLoss: 设在关键支撑位下方1-2个价位
  - takeProfit: 根据2:1或3:1盈亏比计算
  - ⭐ measureMove: 计算突破前高点后的目标位（突破点 + 前期波动区间距离）
- **做空(short)**:
  - stopLoss: 设在关键阻力位上方1-2个价位
  - takeProfit: 根据2:1或3:1盈亏比计算
  - ⭐ measureMove: 计算跌破前低点后的目标位（突破点 - 前期波动区间距离）

**Measure Move计算示例**:
- 做多: 前高2500，前低2300，区间200点。当前突破2500，则measureMove = 2500 + 200 = 2700
- 做空: 前低2500，前高2700，区间200点。当前跌破2500，则measureMove = 2500 - 200 = 2300

⭐⭐⭐ **JSON格式要求（必须包含所有字段）**:
- 当action为hold时
返回JSON: {{"action":"hold","reason":"详细中文分析","stopLoss": 数字,"takeProfit": 数字,"measureMove": 数字,"supportResistanceLevel": 价格}}
- 当action为long或short时
返回JSON: {{"action":"long/short","reason":"详细中文分析","stopLoss": 数字,"takeProfit": 数字,"measureMove": 数字,"supportResistanceLevel": 价格,"open_price":价格}}
- 当action为close时
返回JSON: {{"action":"close","reason":"详细中文分析","stopLoss": 数字,"takeProfit": 数字,"measureMove": 数字,"supportResistanceLevel": 价格,"open_price":价格,"close_price":"价格"}}
- 注意在平仓时的理由要判断当前持仓的方向，分清楚是止盈还是止损，给出理由，不要出现南辕北辙的描述

⚠️ **字段说明**:
- **supportResistanceLevel**: 关键支撑/阻力位价格（开仓参考位，无可省略）
- **其他字段**: 按原有要求填写



⚠️ **决策要求（重要！）**:
- **当确定要开仓**: action必须为"long"或"short"，**不要**说"建议做多"！"可能"！"观望"！
- **当价格触及开仓时AI建议的stopLoss/takeProfit/measureMove**: **必须返回"close"**，不要犹豫！
- **当确定要平仓**: action必须为"close"
- **当不确定时**: action必须为"hold"（**不要**说"建议观望"！"等待"！）
- ⭐ **特别重要**: 如果当前价格已经触及或超越开仓时建议的measureMove或takeProfit，**必须返回"close"平仓止盈！不得返回"hold"！**

⭐⭐⭐ **Measure Move是AI价值的体现，请务必计算并返回！**
"""

        return prompt

    def _call_deepseek_vision_analysis(self, chart_path: str, prompt: str) -> Dict:
        """
        调用DeepSeek进行视觉分析

        Args:
            chart_path: 图片路径
            prompt: 提示词

        Returns:
            分析结果
        """
        try:
            # 检查API密钥
            if not self.deepseek_api_key:
                # 如果没有API密钥，返回模拟结果
                return {
                    "action": "hold",
                    "reason": f"当前时间为 {datetime.now().strftime('%H:%M')}，建议等待更明确的价格行为信号后再做决策。",
                    "stopLoss": None,
                    "takeProfit": None
                }

            # 读取并编码图片
            with open(chart_path, 'rb') as image_file:
                image_data = base64.b64encode(image_file.read()).decode('utf-8')

            # 构建请求
            headers = {
                'Authorization': f'Bearer {self.deepseek_api_key}',
                'Content-Type': 'application/json'
            }

            # 尝试多种 DeepSeek 图片格式
            # 格式1: 尝试 deepseek-chat 视觉版本
            try:
                payload = {
                    "model": "deepseek-chat",
                    "messages": [
                        {
                            "role": "user",
                            "content": [
                                {
                                    "type": "text",
                                    "text": prompt
                                },
                                {
                                    "type": "image_url",
                                    "image_url": {
                                        "url": f"data:image/png;base64,{image_data}"
                                    }
                                }
                            ]
                        }
                    ],
                    "max_tokens": 1500,
                    "temperature": 0.1
                }
                response = requests.post(
                    self.deepseek_api_url,
                    headers=headers,
                    json=payload,
                    timeout=60
                )

                if response.status_code == 200:
                    result = response.json()
                    content = result['choices'][0]['message']['content']
                    return self._parse_analysis_result(content)

            except Exception as e:
                # 格式1失败，继续尝试格式2
                pass

            # 格式2: 尝试使用 message 中的 image_content 字段
            try:
                payload = {
                    "model": "deepseek-chat",
                    "messages": [
                        {
                            "role": "user",
                            "content": prompt,
                            "image_content": f"data:image/png;base64,{image_data}"
                        }
                    ],
                    "max_tokens": 1500,
                    "temperature": 0.1
                }
                response = requests.post(
                    self.deepseek_api_url,
                    headers=headers,
                    json=payload,
                    timeout=60
                )

                if response.status_code == 200:
                    result = response.json()
                    content = result['choices'][0]['message']['content']
                    return self._parse_analysis_result(content)

            except Exception as e:
                # 格式2失败，继续尝试格式3
                pass

            # 格式3: 使用纯文本分析（降级方案）
            return self._fallback_text_analysis(prompt, klines)

        except Exception as e:
            # 所有格式都失败，返回错误信息
            return {
                "action": "hold",
                "reason": f"AI视觉分析失败: {str(e)}",
                "error": str(e),
                "stopLoss": None,
                "takeProfit": None
            }

    def _parse_analysis_result(self, content: str) -> Dict:
        """解析AI分析结果"""
        try:
            # 尝试直接解析JSON
            analysis = json.loads(content)

            # 为 long/short 添加默认值
            if analysis.get('action') in ['long', 'short', 'Long', 'Short', 'LONG', 'SHORT']:
                # 如果是 long 或 short，确保有 stopLoss 和 takeProfit
                if 'stopLoss' not in analysis:
                    analysis['stopLoss'] = None
                if 'takeProfit' not in analysis:
                    analysis['takeProfit'] = None
                # v0.2.9: 新增字段
                if 'measureMove' not in analysis:
                    analysis['measureMove'] = None
                if 'breakoutPrice' not in analysis:
                    analysis['breakoutPrice'] = None
                if 'supportResistanceLevel' not in analysis:
                    analysis['supportResistanceLevel'] = None
            else:
                # 如果是 hold 或 close，确保字段存在但不强制
                analysis.setdefault('stopLoss', None)
                analysis.setdefault('takeProfit', None)
                analysis.setdefault('measureMove', None)
                analysis.setdefault('breakoutPrice', None)
                analysis.setdefault('supportResistanceLevel', None)

            return analysis
        except json.JSONDecodeError:
            # 如果失败，检查是否是markdown代码块格式
            try:
                # 查找 ```json 或 ``` 代码块
                import re
                json_pattern = r'```(?:json)?\s*\n?(.*?)\n?```'
                match = re.search(json_pattern, content, re.DOTALL)

                if match:
                    # 提取代码块中的JSON并解析
                    json_content = match.group(1).strip()
                    analysis = json.loads(json_content)

                    # 为 long/short 添加默认值
                    if analysis.get('action') in ['long', 'short', 'Long', 'Short', 'LONG', 'SHORT']:
                        if 'stopLoss' not in analysis:
                            analysis['stopLoss'] = None
                        if 'takeProfit' not in analysis:
                            analysis['takeProfit'] = None
                        # v0.2.9: 新增字段
                        if 'measureMove' not in analysis:
                            analysis['measureMove'] = None
                        if 'breakoutPrice' not in analysis:
                            analysis['breakoutPrice'] = None
                        if 'supportResistanceLevel' not in analysis:
                            analysis['supportResistanceLevel'] = None
                    else:
                        analysis.setdefault('stopLoss', None)
                        analysis.setdefault('takeProfit', None)
                        analysis.setdefault('measureMove', None)
                        analysis.setdefault('breakoutPrice', None)
                        analysis.setdefault('supportResistanceLevel', None)

                    return analysis
                else:
                    # 没找到代码块，返回错误信息
                    return {
                        "action": "hold",
                        "reason": "分析结果格式异常，建议人工判断",
                        "stopLoss": None,
                        "takeProfit": None,
                        "measureMove": None,
                        "breakoutPrice": None,
                        "supportResistanceLevel": None
                    }
            except Exception as e:
                # 所有解析都失败，返回错误信息
                return {
                    "action": "hold",
                    "reason": "分析结果格式异常，建议人工判断",
                    "stopLoss": None,
                    "takeProfit": None,
                    "measureMove": None,
                    "breakoutPrice": None,
                    "supportResistanceLevel": None
                }

    def _fallback_text_analysis(self, prompt: str, klines: list) -> Dict:
        """降级文本分析方案"""
        if not klines:
            return {
                "action": "hold",
                "reason": "缺乏K线数据"
            }

        latest = klines[-1]
        price_change_pct = ((latest['close'] - klines[0]['open']) / klines[0]['open']) * 100

        data_summary = f"""
根据以下市场数据进行分析：
- 开盘价: {latest.get('open', 0):.2f}
- 最高价: {latest.get('high', 0):.2f}
- 最低价: {latest.get('low', 0):.2f}
- 收盘价: {latest.get('close', 0):.2f}
- 成交量: {latest.get('volume', 0):.2f}

价格变化: {price_change_pct:.2f}%
"""
        full_prompt = f"{prompt}\n\n{data_summary}\n\n请基于以上信息分析。"

        payload = {
            "model": "deepseek-chat",
            "messages": [
                {
                    "role": "user",
                    "content": full_prompt
                }
            ],
            "max_tokens": 500,
            "temperature": 0.3
        }

        headers = {
            'Authorization': f'Bearer {self.deepseek_api_key}',
            'Content-Type': 'application/json'
        }

        response = requests.post(
            self.deepseek_api_url,
            headers=headers,
            json=payload,
            timeout=60
        )

        if response.status_code == 200:
            result = response.json()
            content = result['choices'][0]['message']['content']
            return self._parse_analysis_result(content)
        else:
            return {
                "action": "hold",
                "reason": f"文本分析失败: {response.status_code} - {response.text}",
                "error": f"{response.status_code}: {response.text}",
                "stopLoss": None,
                "takeProfit": None
            }
